﻿using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace FIFP.Common
{
    //Taken from http://blog.quantumbitdesigns.com/2008/07/22/wpf-cross-thread-collection-binding-part-4-the-grand-solution/
    /// <summary>
    /// Represents a list that allows cross thread collection and property binding.
    /// Use AcquireLock for multithreaded scenarios.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class ObservableList<T> : IList<T>
    {
        #region Fields

        /// <summary>
        /// The dispatcher that is used to notify the UI thread of changes
        /// </summary>
        private IDispatcher _dispatcher;

        /// <summary>
        /// The list used by a worker thread
        /// </summary>
        private List<T> _list;

        /// <summary>
        /// The ObservableCollection that UI controls should bind to
        /// </summary>
        private ObservableCollection<T> _observableCollection;

        #endregion Fields

        #region Constructors

        /// <summary>
        /// Creates a new instance of the ObservableBackgroundList class
        /// </summary>
        /// <param name="dispatcher">The dispatcher that is used to notify the UI thread of changes</param>
        public ObservableList(IDispatcher dispatcher)
        {
            _dispatcher = dispatcher;
            _list = new List<T>();
            _observableCollection = new ObservableCollection<T>();
        }

        #endregion Constructors

        #region Properties

        /// <summary>
        /// Gets the number of elements actually contained in the List.
        /// </summary>
        public int Count
        {
            get
            {
                return _list.Count;
            }
        }

        /// <summary>
        /// Gets the ObservableCollection that UI controls should bind to
        /// </summary>
        public virtual ObservableCollection<T> ObservableCollection
        {
            get
            {
                return _observableCollection;
            }
        }

        bool ICollection<T>.IsReadOnly
        {
            get
            {
                return false;
            }
        }

        #endregion Properties

        #region Indexers

        /// <summary>
        /// Gets or sets the element at the specified index.
        /// </summary>
        /// <param name="index">The zero-based index of the element to get or set.</param>
        /// <returns>The element at the specified index.</returns>
        public T this[int index]
        {
            get
            {
                return _list[index];
            }
            set
            {
                _list[index] = value;
                
                _dispatcher.BeginInvoke(SetItemFromDispatcherThread,
                    index,
                    value
                );
            }
        }

        #endregion Indexers

        #region Public Methods
                
        /// <summary>
        /// Adds an object to the end of the List.
        /// </summary>
        /// <param name="item">The object to be added to the end of the List</param>
        public void Add(T item)
        {
            _list.Add(item);

            _dispatcher.BeginInvoke(AddFromDispatcherThread,
                item
            );
        }

        /// <summary>
        /// Removes all elements from the List
        /// </summary>
        public void Clear()
        {
            _list.Clear();
            _dispatcher.BeginInvoke(ClearFromDispatcherThread
            );
        }

        /// <summary>
        /// Determines whether an element is in the List
        /// </summary>
        /// <param name="item">The object to locate in the List</param>
        /// <returns>true if item is found in the List; otherwise, false</returns>
        public bool Contains(T item)
        {
            return _list.Contains(item);
        }

        /// <summary>
        /// Copies the entire List to a compatible one-dimensional
        /// array, starting at the specified index of the target array.
        /// </summary>
        /// <param name="array">The one-dimensional System.Array that is the destination of the elements
        /// copied from System.Collections.Generic.List<T>. The System.Array must have
        /// zero-based indexing.</param>
        /// <param name="arrayIndex">The zero-based index in array at which copying begins.</param>
        public void CopyTo(T[] array, int arrayIndex)
        {
            _list.CopyTo(array, arrayIndex);
        }

        /// <summary>
        /// Returns an enumerator that iterates through the List
        /// </summary>
        /// <returns>A System.Collections.Generic.List<T>.Enumerator for the System.Collections.Generic.List<T>.</returns>
        public IEnumerator<T> GetEnumerator()
        {
            return _list.GetEnumerator();
        }

        /// <summary>
        /// Searches for the specified object and returns the zero-based index of the
        /// first occurrence within the entire List.
        /// </summary>
        /// <param name="item">The object to locate in the List</param>
        /// <returns>The zero-based index of the first occurrence of item within the entire List
        /// if found; otherwise, –1.</returns>
        public int IndexOf(T item)
        {
            return _list.IndexOf(item);
        }

        /// <summary>
        /// Inserts an element into the List at the specified index.
        /// </summary>
        /// <param name="index">The zero-based index at which item should be inserted.</param>
        /// <param name="item">The object to insert. The value can be null for reference types.</param>
        public void Insert(int index, T item)
        {
            _list.Insert(index, item);
            _dispatcher.BeginInvoke(InsertItemFromDispatcherThread,
                index,
                item);
        }               

        /// <summary>
        /// Removes the first occurrence of a specific object from the List.
        /// </summary>
        /// <param name="item">The object to remove from the List.</param>
        /// <returns>true if item is successfully removed; otherwise, false. This method also
        ///     returns false if item was not found in the List</returns>
        public bool Remove(T item)
        {
            bool result = _list.Remove(item);

            //only remove the item from the UI collection if it is removed from the worker collection
            if (result)
            {
                _dispatcher.BeginInvoke(RemoveFromDispatcherThread,
                    item
                );
            }
            return result;
        }

        /// <summary>
        /// Removes the element at the specified index of the List
        /// </summary>
        /// <param name="index">The zero-based index of the element to remove.</param>
        public void RemoveAt(int index)
        {
            _list.RemoveAt(index);
            _dispatcher.BeginInvoke(
                RemoveAtFromDispatcherThread,
                index
            );
        }

        #endregion Public Methods

        #region Private Methods

        private void AddFromDispatcherThread(T item)
        {
            _observableCollection.Add(item);
        }

        private void ClearFromDispatcherThread()
        {
            _observableCollection.Clear();
        }

        //change callbacks
        private void InsertItemFromDispatcherThread(int index, T item)
        {
            _observableCollection.Insert(index, item);
        }

        private void RemoveAtFromDispatcherThread(int index)
        {
            _observableCollection.RemoveAt(index);
        }

        private void RemoveFromDispatcherThread(T item)
        {
            _observableCollection.Remove(item);
        }

        private void SetItemFromDispatcherThread(int index, T item)
        {
            _observableCollection[index] = item;
        }

        #endregion Private Methods

        #region Other

        /// <summary>
        /// Returns an enumerator that iterates through the List
        /// </summary>
        /// <returns>Am Enumerator for the List.</returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return _list.GetEnumerator();
        }

        #endregion Other
    }
}